﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace Jhong.FunMVC
{
    internal class ActionInvoker
    {
        public void InvokeAction(ControllerContext context)
        {
            this._context = context;
            var targetAction = this.FindAction();
            // targetAction.Invoke(this._context.Controller, new object[] { });
            var parameters = ModelBinder.GetBindParameters(this._context.Request, targetAction);
            var dynamicMethod = new DynamicMethod(Guid.NewGuid().ToString("N"), typeof(ActionResult), new[] { typeof(Controller), typeof(BindParameter[]) });
            var il = dynamicMethod.GetILGenerator();
            for (int i = 0; i < parameters.Count(); i++)
            {
                var p = parameters[i];
                il.DeclareLocal(p.Type);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Ldc_I4_S, i);
                il.Emit(OpCodes.Ldelem_Ref);
                il.Emit(OpCodes.Callvirt, typeof(BindParameter).GetProperty("Value").GetGetMethod());
                if (p.Type.IsValueType) il.Emit(OpCodes.Unbox_Any, p.Type);
                else il.Emit(OpCodes.Isinst, p.Type);
                il.Emit(OpCodes.Stloc, i);
            }
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Isinst, this._context.ControllerType);
            for (int i = 0; i < parameters.Count(); i++)
            {
                il.Emit(OpCodes.Ldloc, i);
            }
            il.Emit(OpCodes.Callvirt, targetAction);
            il.Emit(OpCodes.Ret);
            var actionDelegate = (Func<Controller, BindParameter[], ActionResult>)dynamicMethod.CreateDelegate(typeof(Func<Controller, BindParameter[], ActionResult>));
            var result = actionDelegate(this._context.Controller, parameters);
            //var result = targetAction.Invoke(this._context.Controller, parameters);
            if (null != result)
            {
                // var r2 = result as ActionResult;
                //if (null != r2) r2.Execute();
                result.Execute();
            }
            var outputCacheAttr = Attribute.GetCustomAttribute(targetAction, typeof(OutputCacheAttribute));
            if (null != outputCacheAttr)
            {
                var duration = (outputCacheAttr as OutputCacheAttribute).Duration;
                ResponseCache.AddCache(this._context.Request.Uri.AbsoluteUri, this._context.Response, duration);
            }
        }

        private ControllerContext _context;

        private MethodInfo FindAction()
        {
            var methods = from m in this._context.ControllerType.GetMethods() where m.Name.Equals(this._context.Request.Action, StringComparison.OrdinalIgnoreCase) select m;
            MethodInfo candidate = null;
            foreach (var method in methods)
            {
                var httpMethodAttribute = Attribute.GetCustomAttribute(method, typeof(HttpMethodAttribute));
                if (null == httpMethodAttribute)
                {
                    candidate = method;
                    continue;
                }
                var httpMethod = (httpMethodAttribute as HttpMethodAttribute).HttpMethod.ToString();
                if (this._context.Request.Method.IndexOf(httpMethod) < 0) continue;
                return method;
            }
            if (null != candidate) return candidate;
            throw new PageNotFoundException();
        }
    }
}
